package
{	
	import Box2D.Collision.Shapes.b2PolygonShape;
	import Box2D.Collision.b2WorldManifold;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.Contacts.b2ContactEdge;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2DebugDraw;
	import Box2D.Dynamics.b2Fixture;
	import Box2D.Dynamics.b2FixtureDef;
	import Box2D.Dynamics.b2World;
	
	import com.util.Camera;
	
	import flash.desktop.NativeApplication;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.EventDispatcher;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.SoundMixer;
	import flash.media.SoundTransform;
	
	import levelloader.ObjectLayerData;
	
	import starling.core.Starling;
	import starling.display.Image;
	import starling.display.Sprite;
	import starling.events.EnterFrameEvent;
	import starling.events.Event;
	import starling.textures.Texture;
	
	public class Game extends Sprite
	{
		public static const pixelsPerMeter:Number = 30;
		private const timeStep:Number	= 1.0 / 30.0;
		private const iterations:Number	= 10;
		private const hero_speed:Number	= 2;		
		private const hero_maxSpeed:Number	= 3;
		private const hero_jump:Number	= -10;
		
		private static const GAME_UI_LOADING:uint = 0;
		private static const GAME_UI:uint = 1;
		private static const GAME_LEVEL_LOADING:uint = 2;
		private static const GAME_LEVEL_PLAY:uint = 3;
		private static const GAME_LOADER_LOADING:uint = 4;
		private static const GAME_PAUSED:uint = 5;
		
		public static var season:uint = 1;
		public static var level:uint = 3;
		//private var _camera:StarlingCameraFocus;
		private var _camera:Camera;
		
		private var _gameState:uint;
		private var _world:World;
		private var _uiManager:UIManager;
		private var _hud:HUD;
		private var _loaderScreen:Image;				

		private var ourWorld:b2World;
		private var debugDraw:b2DebugDraw;						
		private var physBody:b2Body;
		private var _cameraStop:Boolean;
		private var heroNormal:b2Vec2;
		private var _canHeroJump:Boolean;
		private var _isHeroStuck:Boolean;
		private var inSecondJump:Boolean;
		private var _channel:SoundChannel;
		private var _sound:Sound;
				
		private function initDebugDraw():void
		{
			debugDraw = new b2DebugDraw();							
			//debugDraw.SetSprite(BunkysAijou.debugSprite);
			debugDraw.SetDrawScale(pixelsPerMeter);
			debugDraw.SetFillAlpha(.3);
			debugDraw.SetLineThickness(1.0);
			debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);			
			ourWorld.SetDebugDraw(debugDraw);			
		}
				
		/**
		 * Creates the physics world.
		 */
		private function createWorld():void
		{
			// Horizontal gravity of 10
			var gravity:b2Vec2 = new b2Vec2(0.0, 10.0);
			var doSleep:Boolean = true;
			
			
			ourWorld = new b2World(gravity, doSleep);	
			
			heroNormal = new b2Vec2();
			inSecondJump = false;
			// Register custom contact listener for one-sided platforms
			//ourWorld.SetContactListener(new CustomContactListener());
		}

		public function isHeroStuck():Boolean
		{
			return _isHeroStuck;
		}

		public function canHeroJump():Boolean
		{
			return _canHeroJump;
		}
		
		private function destroyWorld():void
		{
			var body:b2Body = ourWorld.GetBodyList();

			while(body)
			{
				var temp:b2Body = body.GetNext();				
				ourWorld.DestroyBody(body);
				body = temp;
			}
			
			ourWorld = null;
		}
		

		private function createStaticCollidables():void
		{
			var mapObjectGroup:ObjectLayerData = _world.getTMXTileMap().objectGroups()[0];
			
			for(var idx:uint = 0; idx < mapObjectGroup.mapObjects.length; ++idx)
			{
				var objdata:Object = mapObjectGroup.mapObjects[idx];
				createBox(objdata.x, objdata.y, objdata.width, objdata.height, false, null, 1, 0);
			}			
		}
		public function removePhysBody(body:b2Body):void
		{
			if(ourWorld != null)
				ourWorld.DestroyBody(body);	
		}
		
		public function createBox(x:Number, y:Number, 
								   width:Number, height:Number, 
								   isDynamic:Boolean,
								   name:String = null,
								   density:Number = 1, friction:Number = .5, 
								   fixedRotation:Boolean = true):b2Body
		{
			var bWidth:Number = p2m(width);
			var bHeight:Number = p2m(height);
			
			// box shape
			var shape:b2PolygonShape = new b2PolygonShape();
			shape.SetAsBox(bWidth / 2, bHeight / 2);
		
			// fixture
			var fixture:b2FixtureDef = new b2FixtureDef();
			fixture.density = density;
			fixture.friction = friction;
			fixture.shape = shape;
			fixture.userData = name;
			
			if(name != null)
			{
				if(name == "enemy")
				{
					fixture.filter.categoryBits = 0x0002;
					fixture.filter.maskBits = 0xFFFF & ~0x0002 & ~0x0004;					
				}
				else if(name == "hero")
				{
					fixture.filter.categoryBits = 0x0004;
					fixture.filter.maskBits = 0xFFFF & ~0x0002 & ~0x0004;										
				}
			}
			else 
			{
				fixture.filter.categoryBits = 0x0008;
				fixture.filter.maskBits = 0xFFFF;
			}
			
			// body definition
			var bodyDef:b2BodyDef = new b2BodyDef();
			bodyDef.position.Set(p2m(x) + (bWidth / 2), p2m(y) + (bHeight / 2));
			bodyDef.type = isDynamic ? b2Body.b2_dynamicBody : b2Body.b2_staticBody;
			bodyDef.fixedRotation = fixedRotation;
			// body
			var body:b2Body = ourWorld.CreateBody(bodyDef);
			body.CreateFixture(fixture);
			return body;
		}
		
		private function updatePhysics(e:Event = null):void
		{
			heroNormal.x = heroNormal.y = 0;			
			updateHeroCollisions();

			ourWorld.Step(timeStep, iterations, iterations);
			ourWorld.ClearForces();			
			//ourWorld.DrawDebugData();			
		}
		
		private function updateHeroCollisions():void
		{
			var manifold:b2WorldManifold;
			var collisionNormal:b2Vec2 = new b2Vec2();
			
			
			var aabbCollisions:String = "";
			var fixtureCollisions:String = "";		
			
			
			_canHeroJump = false;
			_isHeroStuck = false;
			
			// Iterate through contact lists - all collisions that hero currently have
			for (var edge:b2ContactEdge = physBody.GetContactList(); edge; edge = edge.next)
			{
				manifold = new b2WorldManifold();
				edge.contact.GetWorldManifold(manifold);
				collisionNormal = manifold.m_normal;
				
				
				// We still don't know whether our hero is fixtureA or fixtureB
				var fixtureA:b2Fixture = edge.contact.GetFixtureA();
				var fixtureB:b2Fixture = edge.contact.GetFixtureB();
				
				
				var nameA:String = fixtureA.GetUserData() ? (fixtureA.GetUserData() as String): "wall";
				var nameB:String = fixtureB.GetUserData() ? (fixtureB.GetUserData() as String): "wall";
								
				
				// If hero is in fixtureB than the normal is calculated from the colliding object's point of view
				// so to have consistent normals for hero in fixture A and B, we multiply it by minus one.
				if (nameB == "hero")
				{
					collisionNormal.x *= -1;
					collisionNormal.y *= -1;
				}
				
				
				// If we got here it means there is a bounding box collision (AABB).
				// To make sure that shapes are colliding we use IsTouching method.
				if (edge.contact.IsTouching())
				{					
					// If the normal vertical value is greater than zero it means that some object is pushing the hero up.
					// In other words, the hero is standing in something.
					if (collisionNormal.y > 0)
					{
						_canHeroJump = true;
						inSecondJump = false;
					}
					
					if(collisionNormal.x > 0)
					{
						_isHeroStuck = true;
					}
										
					heroNormal = collisionNormal;
				}
			}						
		}
		
		public function Game()
		{
			super();			
			this.addEventListener(starling.events.Event.ADDED_TO_STAGE, onAddedToStage);
		}
		
		private function onAddedToStage(event:Event):void
		{											
			this.removeEventListener(starling.events.Event.ADDED_TO_STAGE, null);
			this.addEventListener(GameModeChangedEvent.GAME_PAUSE, onGamePaused);
			this.addEventListener(GameModeChangedEvent.GAME_LEVEL_RESTART, onLevelStart);
			this.addEventListener(GameModeChangedEvent.GAME_LEVEL_QUIT, onLevelEnd);
			this.addEventListener(BunkyEvent.BUNKY_DAMAGE, onBunkyDamage);
			this.addEventListener(BunkyEvent.COLLECT_REWARD, onBunkyCollectReward);			
			this.addEventListener(BunkyDeathEvent.BUNKY_LEVEL_FINISH, onBunkyLevelFinish);
			this.addEventListener(PlayerActionEvent.ATTACK_ACTION, onPlayerAction);
			this.addEventListener(PlayerActionEvent.JUMP_ACTION, onPlayerAction);			
			this.addEventListener(starling.events.Event.ENTER_FRAME, onEnterFrame);			
			
			NativeApplication.nativeApplication.addEventListener(flash.events.Event.ACTIVATE, handleActivate, false, 0, true);			
			NativeApplication.nativeApplication.addEventListener(flash.events.Event.DEACTIVATE , handleDeactivate, false, 0, true);
			
			
			_loaderScreen = new Image(Assets.getTexture("LoadingTexture"));
			addChild(_loaderScreen);									
			_gameState = GAME_LOADER_LOADING;
		}				
		
		private function handleDeactivate(event:flash.events.Event):void 
		{
			//the app is now losing focus
			//musicChannel.stop();
			if(_channel)
				_channel.stop();
		}
						
		private function handleActivate(event:flash.events.Event):void 
		{
			//musicChannel= mySong.play();
		}

		private function onGamePaused(e:Event):void		
		{				
			if(_gameState == GAME_PAUSED)
			{						
				_world.pause(false);
				_gameState = GAME_LEVEL_PLAY;
			}				
			else				
			{				
				_world.pause(true);
				_gameState = GAME_PAUSED;	
			}
			
		}
		
/*		private function onDataLoaded():void
		{
			_gameState = GAME_LOADER_LOADING;			
			this.addEventListener(starling.events.Event.ENTER_FRAME, onEnterFrame);			
		}
*/		
		private function onEnterFrame(event:EnterFrameEvent):void
		{
			switch(_gameState)
			{
				case GAME_LOADER_LOADING:
					_gameState = GAME_UI_LOADING;
					break;
				case GAME_UI_LOADING:
				{
					if(_world != null)
					{					
						destroyWorld();
						_world.dispose();
						_world.removeFromParent(false);						
						_world = null;
						
						_hud.dispose();
						_hud.removeFromParent(false);						
						_hud = null;
						
						_cameraStop = false;
						//camera.destroy();
						_camera = null;
						
						Assets.dispose();
					}
					else
					{
						//display loading screen and setup
						//UI screen.						
						if(_uiManager == null)
						{
							_uiManager = new UIManager();	
						}						
						addChild(_uiManager);
						_gameState = GAME_UI;
						removeChild(_loaderScreen);
					}
					break;
				}
					/** Transitioning Code **/
					// notice we are now calling onEnterFrame function on our
					// uiManager object and it will only be called when Game is 
					//in GAME_UI mode, not when level is playing.
				case GAME_UI:
				{
					_uiManager.onEnterFrame(event.passedTime);
					break;
				}
										
				case GAME_LEVEL_LOADING:
				{					
					if(_world != null)
					{					
						destroyWorld();
						_world.dispose();
						_world.removeFromParent(false);						
						_world = null;
						
						_hud.dispose();
						_hud.removeFromParent(false);						
						_hud = null;
						
						_cameraStop = false;
						//camera.destroy();
						_camera = null;
						
						Assets.dispose();
					}

					createWorld();

					_world = new World();
					addChild(_world);	
					_hud = new HUD();
					addChild(_hud);

					physBody = createBox(20, 672, 65, 70, true, "hero");
					//initDebugDraw();
					createStaticCollidables();

					_camera = new Camera(20, 320, _world._bunky);
/*					_camera = new StarlingCameraFocus( Starling.current.stage, _world, _world._bunky, [	{name:Assets.LAYER_NAME[Assets.BACKGROUND],instance:_world.thirdPlane,ratio:0.5},																								
																										{name:Assets.LAYER_NAME[Assets.MIDLAYER],instance:_world.secondPlane,ratio:0.2},
																										{name:Assets.LAYER_NAME[Assets.PLATFORM],instance:_world.firstPlane,ratio:0}																										
																										], false );										
					camera.setFocusPosition(20, Starling.current.stage.stageHeight - Starling.current.stage.stageHeight * 0.5);
					//camera.setBoundary(_world.firstPlane);
					camera.start();
*/					_gameState = GAME_LEVEL_PLAY;						
					break;
				}	
					
				case GAME_LEVEL_PLAY:
					//camera.shake(0.01, 4*60);
					if(physBody.GetLinearVelocity().x < hero_maxSpeed)
						physBody.ApplyImpulse(new b2Vec2(hero_speed, 0), physBody.GetWorldCenter());
					
					updatePhysics();

					var physPos:b2Vec2 = physBody.GetPosition();
					_world._bunky.x = m2p(physPos.x) - _world._bunky.width/2; 
					_world._bunky.y = m2p(physPos.y) - _world._bunky.height/2;
					
					if(!_cameraStop)
					{
						camera.update();						
					}
					
					//transform camera target object from world_space to view_space.
					_world._bunky.x -= _camera.getCameraPosX();
					_world._bunky.y -= _camera.getCameraPosY();

					_world.onEnterFrame(event.passedTime);
					_hud.onEnterFrame(event.passedTime);
						
					
					//BunkysAijou.debugSprite.x = -_camera.getCameraPosX();
					//BunkysAijou.debugSprite.y = -_camera.getCameraPosY();
					break;
				case GAME_PAUSED:					
					break;				
				default:				
			}			
		}
	
		public function onLevelEnd():void
		{
			_channel.stop();
			//change game state.	
			_gameState = GAME_UI_LOADING;
			//_channel.stop();
		}
		
		public function onLevelStart():void
		{
			_channel = Assets.SoundStore2.play(0,999);
			//_sound = new Sound(new Assets[Assets.SOUNDS_LEVELS[(Game.season-1)*3 + Game.level]]());
			
			//change game state.
			if(_gameState == GAME_PAUSED)
			{						
				_world.pause(false);
			}
			//_channel = Assets.SoundStore.play(0,999);
			_gameState = GAME_LEVEL_LOADING;
		}
		
		private function onBunkyDamage(e:Event):void
		{
			Assets.SoundStore5.play();
			_hud.updateBunkyHealth();			
		}
		
		private function onBunkyCollectReward(e:Event):void
		{	
			Assets.SoundStore6.play();
			_hud.collectable_counter();			
		}

		private function onBunkyLevelFinish(e:Event):void
		{
			
			_cameraStop = true;			
		}
		
		private function onPlayerAction(e:Event):void
		{			
			if(e.type == PlayerActionEvent.ATTACK_ACTION)
			{			
				var obj:bullet = new bullet(_world._bunky);				
				Assets.SoundStore4.play();
				if(obj != null)
				{
					_world.addChild(obj);
				}
			}
			else if(e.type == PlayerActionEvent.JUMP_ACTION)
			{
				if(_canHeroJump || (!inSecondJump))
				{
					Assets.SoundStore3.play();
					if(!_canHeroJump)	inSecondJump = true;
					physBody.SetLinearVelocity(new b2Vec2(physBody.GetLinearVelocity().x, hero_jump));
				}										
			}
			
			_world._bunky.handleInput(e.type);
		}
		
		private function p2m(pixels:Number):Number
		{
			return pixels / pixelsPerMeter;
		}
		
		private function m2p(meters:Number):Number
		{
			return meters * pixelsPerMeter;
		}

/*		public function get camera():StarlingCameraFocus
		{
			return _camera;
		}
*/

		public function get camera():Camera
		{
			return _camera;
		}
	}				
}